﻿#include "widget.h"
#include "ui_widget.h"

#include "helpinfo.h"

#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QtGlobal>
#include <QComboBox>

#include <QSerialPortInfo>

#include "crcCheck.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    /* 先设置界面的显示 */
    useruiInit();
    /* 初始化串口 */
    serialInit();
    /* 初始化定时器线程 10ms */
    threadTimer = new QTimer;
    connect(threadTimer, &QTimer::timeout, this, &Widget::on_ThreadFunction);
    threadTimer->start(10);

    /* 初始化超时定时器 */
    timeOutTimer = new QTimer;
    connect(timeOutTimer, &QTimer::timeout, this, &Widget::on_TimeOutFunction);
    /* 初始化完成后不要打开 */
    timeOutTimer->stop();
}

Widget::~Widget()
{
    delete ui;
}

/**
 * @brief Widget::useruiInit
 * @author yuluo
 * @details 用户界面初始化 注意在设置样式时不要重复条用setStyleSheet
 * 设置同一个控件,重复调用会覆盖前面的内容
 */
void Widget::useruiInit()
{
    /* ========================================帮助按钮======================================== */
    /* 设置帮助按钮的边框为无边框 */
    ui->btn_help->setStyleSheet("border: none;");
    /* 设置鼠标悬停在帮助按钮的提示 */
    ui->btn_help->setToolTip("查看文档");
    /* 连接槽函数 */
    connect(ui->btn_help, &QPushButton::clicked, this, &Widget::onBtnClickedSlots);

    /* ========================================选择固件按钮======================================== */
    /* 设置选择固件按钮的样式 边框2px 圆角6px 鼠标悬停和点击时的背景 */
    ui->btn_selectdfu->setStyleSheet("QPushButton{border:2px solid gray; border-radius:6px;}"
                                     "QPushButton:hover{background-color:rgb(229, 241, 251); color: black;}"
                                     "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}");
    /* 连接槽函数 */
    connect(ui->btn_selectdfu, &QPushButton::clicked, this, &Widget::onBtnClickedSlots);
    firmwareData.clear();

    /* ========================================显示固件名称的标签======================================== */
    /* 在显示文件名的地方先显示filename提示信息 */
    ui->lab_dfuname->setText("firmware name");
    ui->lab_dfuname->setStyleSheet("color: gray");

    /* ========================================CRC下拉框======================================== */
    /* 给CRC下拉框增加条目 */
    ui->cbx_crc->addItem("CRC-32");
    ui->cbx_crc->addItem("CRC-8");
    /* 给这个下拉框添加槽函数 */
    connect(ui->cbx_crc, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Widget::on_CbxCurrentIndexChangedSlots);

    /* 设置CRC选择列表的样式 去掉默认下拉框边框 增加一个图片替代原本的样式 */
    ui->cbx_crc->setStyleSheet("QComboBox{border:2px solid gray; border-radius:6px;padding: 3px 20px 3px 10px;}"
                               "QComboBox::drop-down {border: 0px;}"
                               "QComboBox::hover {background-color:rgb(228, 240, 249); border-color:rgb(4, 121, 213);}"
                               "QComboBox::down-arrow {image: url(:/icon/down.png); width: 12px;height: 12px;margin-right: 5px;}");

    /* ========================================显示CRC计算结果的标签======================================== */
    /* 设置标签内容可选 */
    ui->lab_crcresult->setTextInteractionFlags(Qt::TextSelectableByMouse);
    /* 增加一条线在标签的下面 */
    QFrame *line = new QFrame(this);
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Plain);
    /* 设置这条线的位置和大小 防止没有显示时不显示 */
    line->setGeometry(132, 204, 140, 2);
    /* 设置标签的显示颜色 */
    ui->lab_crcresult->setStyleSheet("color: rgb(0, 0, 255);");
    /* 默认显示0xFFFFFFFF */
    ui->lab_crcresult->setText("0xFFFFFFFF");

    /* ========================================设置串口选择下拉列表的样式======================================== */
    /* 设置CRC选择列表的样式 去掉默认下拉框边框 增加一个图片替代原本的样式 */
    ui->cbx_selectcom->setStyleSheet("QComboBox{border:2px solid gray; border-radius:6px;padding: 3px 20px 3px 10px;}"
                               "QComboBox::drop-down {border: 0px;}"
                               "QComboBox::hover {background-color:rgb(228, 240, 249); border-color:rgb(4, 121, 213);}"
                               "QComboBox::down-arrow {image: url(:/icon/down1.png); width: 12px;height: 12px;margin-right: 5px;}");

    /* ========================================串口参数设置======================================== */
    /* 这里样式就不管了 */
    /* 波特率就放几个常用的 */
    ui->cbx_bond->addItem("2400");
    ui->cbx_bond->addItem("4800");
    ui->cbx_bond->addItem("9600");
    ui->cbx_bond->addItem("115200");
    ui->cbx_bond->addItem("256000");
    ui->cbx_bond->addItem("921600");
    /* 默认选中 115200 */
    ui->cbx_bond->setCurrentText("115200");

    /* 停止位 */
    ui->cbx_stopbit->addItem("1");
    ui->cbx_stopbit->addItem("1.5");
    ui->cbx_stopbit->addItem("2");

    /* 数据位 */
    ui->cbx_databit->addItem("8");
    ui->cbx_databit->addItem("7");
    ui->cbx_databit->addItem("6");
    ui->cbx_databit->addItem("5");

    /* 奇偶校验位 */
    ui->cbx_paritybit->addItem("None");
    ui->cbx_paritybit->addItem("Odd");
    ui->cbx_paritybit->addItem("Even");

    /* ========================================连接设备按钮======================================== */
    /* 注：此按钮本质时打开串口 */
    /* 设置连接设备的按钮 */
    ui->btn_connectdevice->setStyleSheet("QPushButton{border:2px solid gray; border-radius:6px;}"
                                     "QPushButton:hover{background-color:rgb(229, 241, 251); border-color:rgb(4, 121, 213);}"
                                     "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}");

    ui->btn_connectdevice->setIcon(QIcon(":/icon/disconnect.png"));
    /* 连接槽函数 */
    connect(ui->btn_connectdevice, &QPushButton::clicked, this, &Widget::onBtnClickedSlots);

    /* ========================================设备信息标签======================================== */
    /* 提示信息显示位浅灰色 */
    ui->lab_deviceinfo->setStyleSheet("color: gray");

    /* ========================================开始升级固件按钮======================================== */
    ui->btn_startupdate->setStyleSheet("QPushButton{border:2px solid gray; border-radius:6px;}"
                                     "QPushButton:hover{background-color:rgb(229, 241, 251); border-color:rgb(4, 121, 213);}"
                                     "QPushButton:pressed{background-color:rgb(204, 228, 247);border-style: inset;}");
    /* 连接槽函数 */
    connect(ui->btn_startupdate, &QPushButton::clicked, this, &Widget::onBtnClickedSlots);

    /* ========================================设置进度条的样式======================================== */
    /* 设置进度条的范围 */
    ui->bar_progress->setRange(0, 100);
    /* 当前进度为0 */
    ui->bar_progress->setValue(0);
    /* 设置产长度 */
    ui->bar_progress->setFixedWidth(260);
    /* 样式 */
    ui->bar_progress->setStyleSheet(
        "QProgressBar {"
        "   border: 2px solid #2196F3;"     /* 外边框颜色 */
        "   border-radius: 10px;"           /* 圆角 */
        "   text-align: center;"            /* 文字居中 */
        "   height: 20px;"                  /* 高度 */
        "   background: #E3F2FD;"           /* 背景色 */
        "   color: black;"                  /* 文字颜色 */
        "   font: bold 12px 'Microsoft YaHei';"
        "}"
        "QProgressBar::chunk {"
        "   background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
        "               stop:0 #42A5F5, stop:1 #1976D2);" /* 渐变色 */
        "   border-radius: 8px;"            /* 内部圆角 */
        "}"
    );
}

/**
 * @brief Widget::serialInit()
 * @author yuluo
 * @details 串口初始化
 */
void Widget::serialInit()
{
    serial = new mySerial(ui->cbx_selectcom, this);
    /* 连接信号槽 */
    connect(serial, &mySerial::portDisconnectde, this, &Widget::on_SerialConnectFaliSlost);
    connect(serial, &mySerial::portOpend, this, [this](){on_SerialSlost(QStringLiteral("opened"));});
    connect(serial, &mySerial::portRemoved, this, [this](){on_SerialSlost(QStringLiteral("removed"));});
    connect(serial, &mySerial::noAckReceived, this, &Widget::on_SerialNoAckSlost);
    connect(serial, &mySerial::ackReceived, this, &Widget::on_SerialAckSlost);
    connect(serial, &mySerial::checksumError, this, &Widget::on_ChechsumError);
}

/**
 * @brief Widget::connectSerial()
 * @author yuluo
 * @details 连接串口
 */
void Widget::connectSerial()
{
    QString text  = ui->cbx_selectcom->currentText();
    QString portName = text.split("：").first();
    QString baud = ui->cbx_bond->currentText();
    QString stopbit = ui->cbx_stopbit->currentText();
    QString databit = ui->cbx_databit->currentText();
    QString paritybit = ui->cbx_paritybit->currentText();

    /* 转换为串口的参数 */
    QSerialPort::BaudRate baudRate = static_cast<QSerialPort::BaudRate>(baud.toUInt());

    QSerialPort::StopBits stopBits = QSerialPort::OneStop;
    if(stopbit == "1")
    {
        stopBits = QSerialPort::OneStop;
    }
    else if(stopbit == "1.5")
    {
        stopBits = QSerialPort::OneAndHalfStop;
    }
    else if(stopbit == "2")
    {
        stopBits = QSerialPort::TwoStop;
    }

    QSerialPort::DataBits dataBits = QSerialPort::Data8;
    if(databit == "5")
    {
        dataBits = QSerialPort::Data5;
    }
    else if(databit == "6")
    {
        dataBits = QSerialPort::Data6;
    }
    else if(databit == "7")
    {
        dataBits = QSerialPort::Data7;
    }
    else if(databit == "8")
    {
        dataBits = QSerialPort::Data8;
    }

    QSerialPort::Parity parityBits = QSerialPort::NoParity;
    if(paritybit == "None")
    {
        parityBits = QSerialPort::NoParity;
    }
    else if(paritybit == "Odd")
    {
        parityBits = QSerialPort::OddParity;
    }
    else if(paritybit == "Even")
    {
        parityBits = QSerialPort::EvenParity;
    }

    if(serial->openPort(portName, baudRate, dataBits, stopBits, parityBits))
    {
        /* 串口打开成功 屏蔽串口参数的修改 仅能修改串口号 */
        ui->cbx_bond->setDisabled(true);
        ui->cbx_databit->setDisabled(true);
        ui->cbx_stopbit->setDisabled(true);
        ui->cbx_paritybit->setDisabled(true);
    }
    else
    {
        /* 串口打开失败 */
#if DEBUG_ENABLE
        qDebug() << "串口打开失败";
#endif
    }
}

/**
 * @brief Widget::selectFirmware()
 * @author yuluo
 * @details 选择固件
 */
void Widget::selectFirmware()
{
    /* 不管有没有打开文件 当点击选择文件的时候 进度条和升级按钮都需要恢复初始状态 */
    ui->btn_startupdate->setText("开始升级固件");
    ui->bar_progress->setValue(0);

    /* 获取打开的文件路径 */
    QString filepath = QFileDialog::getOpenFileName(
                                                    this,
                                                    tr("选择文件"),
                                                    "/home",
//                                                    tr("Image Files (*.hex *.bin);;All Files (*.*)")
                                                    tr("Image Files (*.hex *.bin)") /* 只允许选择hex和bin文件 */
                                                    );
    /* 取消选择文件 提示 */
    if(filepath.isEmpty())
    {
        /* 取消打开文件需要清空之前的内容 */
//        firmwareData.clear();
        /* 显示取消打开文件的提示 */
        QMessageBox::warning(
                            this,                 /*父窗口*/
                            "警告",                /*窗口标题*/
                            "请先选择一个文件！"     /*提示文本*/
                            );
        if(firmwareData.isEmpty())
        {
            /* 重新显示提示信息 */
            ui->lab_dfuname->setText("firmware name");
            ui->lab_dfuname->setStyleSheet("color: gray");
        }
        return;
    }

    /* 提取文件名用于显示 */
    QString filename;
    QFileInfo fi(filepath);
    /* 获取路径中最后的文件名 */
    filename = fi.fileName();
    /* 获取文件后缀 */
    QString suffix = fi.suffix();
    /* 如果文件不是hex和bin类型的需要退出 */
    if(suffix == "hex")
    {
        firmwareType = 0;
    }
    else if(suffix == "bin")
    {
        firmwareType = 1;
    }
    else
    {
        /* 提示打开的文件类型错误 */
        QMessageBox::warning(
                            this,                 /*父窗口*/
                            "警告",                /*窗口标题*/
                            "不支持此文件类型！"     /*提示文本*/
                            );
        return;
    }

    /* 把文件名显示出来 */
    ui->lab_dfuname->setText(filename);
    ui->lab_dfuname->setStyleSheet("color: rgb(0, 0, 255)");

#if DEBUG_ENABLE
    qDebug("filename %s", filename.toUtf8().constData());
#endif

    /* 打开文件 */
    QFile file(filepath);
    /* 打开文件 */
    if(!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::critical(this, "错误", "无法打开文件: " + filepath);
        return;
    }

    /* 读取文件的全部内容 */
    firmwareData = file.readAll();
    file.close();

    /* 将数据分包装好 */
    firmwarePackets = splitFirmware(firmwareData, 1024);
    /* 获取总包数 */
    totalPackets = firmwarePackets.size();
    /* 初始化已发送的包数 */
    sentPackets = 0;
#if DEBUG_ENABLE
    qDebug() << "总包数:" << totalPackets;
#endif

    updateCrcResult();
}

/**
 * @brief Widget::hexToBin
 * @param fileData
 * @author yuluo
 * @details 将hex转为bin
 * @return
 */
QByteArray Widget::hexToBin(const QByteArray &fileData)
{
    struct Segment { quint32 addr; QByteArray data; };
        QList<Segment> segments;

        quint32 baseAddr = 0;
        QList<QByteArray> lines = fileData.split('\n');

        for (QByteArray line : lines) {
            line = line.trimmed();
            if (line.isEmpty() || line[0] != ':')
                continue;

            bool ok;
            int len = line.mid(1, 2).toInt(&ok, 16);
            quint16 addr = line.mid(3, 4).toUShort(&ok, 16);
            int type = line.mid(7, 2).toInt(&ok, 16);

            if (type == 0x00) {
                // 数据记录
                QByteArray data;
                for (int i = 0; i < len; ++i)
                    data.append(char(line.mid(9 + i * 2, 2).toInt(&ok, 16)));
                quint32 absAddr = baseAddr + addr;
                segments.append({absAddr, data});
            } else if (type == 0x02) {
                // 扩展段地址记录
                baseAddr = line.mid(9, 4).toUInt(&ok, 16) << 4;
            } else if (type == 0x04) {
                // 扩展线性地址记录
                baseAddr = line.mid(9, 4).toUInt(&ok, 16) << 16;
            } else if (type == 0x01) {
                // 文件结束记录
                break;
            }
        }

        if (segments.isEmpty())
            return {};

        // 找出最小、最大地址
        quint32 startAddr = segments.first().addr;
        quint32 endAddr = startAddr;
        for (const auto &seg : segments) {
            if (seg.addr < startAddr) startAddr = seg.addr;
            quint32 segEnd = seg.addr + seg.data.size();
            if (segEnd > endAddr) endAddr = segEnd;
        }

        // 生成连续 bin 数据（用 0xFF 填充空洞）
        QByteArray bin(endAddr - startAddr, char(0xFF));
        for (const auto &seg : segments)
            memcpy(bin.data() + (seg.addr - startAddr), seg.data.data(), seg.data.size());

        return bin;
}

/**
 * @brief Widget::updateCrcResult()
 * @author yuluo
 * @details 更新CRC校验结果
 */
void Widget::updateCrcResult()
{
    uint32_t crc_result = 0xffffffff;
    if(firmwareData.isEmpty())
    {
        ui->lab_crcresult->setText(QString("0x%1").arg(crc_result, 8, 16, QLatin1Char('0')).toUpper());
        return;
    }

    /* 计算crc */
    if(ui->cbx_crc->currentText() == "CRC-8")
    {
        if(firmwareType == 0)
        {
            crc_result = crcCheck::calculate_crc8(hexToBin(firmwareData));
        }
        else if(firmwareType == 1)
        {
            crc_result = crcCheck::calculate_crc8(firmwareData);
        }
    }
    else if(ui->cbx_crc->currentText() == "CRC-32")
    {
        if(firmwareType == 0)
        {
            crc_result = crcCheck::calculate_crc32(hexToBin(firmwareData));
        }
        else if(firmwareType == 1)
        {
            crc_result = crcCheck::calculate_crc32(firmwareData);
        }
    }
    else
    {
        crc_result = 0xffffffff;
    }
    ui->lab_crcresult->setText("0x" + QString("%1").arg(crc_result, 8, 16, QLatin1Char('0')).toUpper());
}

/**
 * @brief Widget::sendFirmwareInfo()
 * @author yuluo
 * @details 发送固件信息
 */
void Widget::sendFirmwareInfo()
{
    /* 获取固件的大小 */
    quint32 firmwareSize = 0;
    firmwareSize = firmwareData.size();

    /* 获取固件的校验和 */
    uint32_t crc_result = 0xffffffff;
    if(ui->cbx_crc->currentText() == "CRC-8")
    {
        if(firmwareType == 0)
        {
            crc_result = crcCheck::calculate_crc8(hexToBin(firmwareData));
        }
        else if(firmwareType == 1)
        {
            crc_result = crcCheck::calculate_crc8(firmwareData);
        }
    }
    else if(ui->cbx_crc->currentText() == "CRC-32")
    {
        if(firmwareType == 0)
        {
            crc_result = crcCheck::calculate_crc32(hexToBin(firmwareData));
        }
        else if(firmwareType == 1)
        {
            crc_result = crcCheck::calculate_crc32(firmwareData);
        }
    }
    else
    {
        crc_result = 0xffffffff;
    }

    QByteArray packet;
    /* 放入文件大小 */
    packet.append(char((firmwareSize >> 24) & 0xFF));
    packet.append(char((firmwareSize >> 16) & 0xFF));
    packet.append(char((firmwareSize >> 8) & 0xFF));
    packet.append(char(firmwareSize & 0xFF));
    /* 放入文件CRC校验和 */
    packet.append(char((crc_result >> 24) & 0xFF));
    packet.append(char((crc_result >> 16) & 0xFF));
    packet.append(char((crc_result >> 8) & 0xFF));
    packet.append(char(crc_result & 0xFF));
    /* 放入文件类型 */
    packet.append(char(firmwareType));

#if DEBUG_ENABLE
    qDebug() << "firmware info:" << packet.toHex(' ');
#endif

    /* 发送固件信息 */
    serial->sendDataPacketRetry(static_cast<quint8>(FUNC_FIRMWAREINFO_CODE),static_cast<quint8>(ACK_FUNC_FIRMWAREINFO_CODE),  packet, ui->cbx_crc->currentText());
}

/**
 * @brief Widget::sendEraseCmd()
 * @author yuluo
 * @details 发送擦除指令
 */
void Widget::sendEraseCmd()
{
    /* 发送固件信息 */
    serial->sendDataPacketRetry(static_cast<quint8>(FUNC_ERASE_CODE),static_cast<quint8>(ACK_FUNC_ERASE_CODE),  QByteArray(), ui->cbx_crc->currentText());
}

/**
 * @brief Widget::splitFirmware(const QByteArray &dat, int packetSize)
 * @param dat 需要分包的数据
 * @param packetSize 每包数据的大小
 * @return 数据包列表
 * @author yuluo
 * @details 分割数据包
 */
QList<QByteArray> Widget::splitFirmware(const QByteArray &dat, int packetSize)
{
    QList<QByteArray> packets;

    quint32 totalSize = dat.size();
    for(quint32 offset = 0; offset < totalSize; offset += packetSize)
    {
        int len = qMin(packetSize, int(totalSize - offset));
        packets.append(dat.mid(offset, len));
    }
    return packets;
}

/**
 * @brief Widget::sendFirmwareData()
 * @author yuluo
 * @details 发送固件
 */
void Widget::sendFirmwareData()
{
    quint8 progress = 0;
    /* 有应答才发送下一包数据 */
    if(sendFirmwareDataAck)
    {
        sendFirmwareDataAck = false;
        if(sentPackets < totalPackets)
        {
#if DEBUG_ENABLE
    qDebug() << "正在发送:" << sentPackets + 1 << " 数据包";
#endif
            /* 发送固件数据 */
            serial->sendDataPacketRetryFrimwareData(static_cast<quint8>(FUNC_FIRMWAREDATA_CODE),static_cast<quint8>(ACK_FUNC_FIRMWAREDATA_CODE),  firmwarePackets[sentPackets], ui->cbx_crc->currentText());
            sentPackets++;
            progress = ((sentPackets  * 100) / totalPackets);
            /* 显示进度 */
            ui->bar_progress->setValue(progress);
        }
        else
        {
            /* 标记是发送完成 等待校验的定时器 */
            whichTimeStrike = 1;
            /* 设置需要等待的应答 */
            serial->setWaitFuncCode(ACK_FUNC_FIRMWARECRCOK_CODE);
            /* 开启定时器 等待擦除完成的指令 超时时间10s */
            timeOutTimer->start(10000);
        }
    }
}

/**
 * @brief Widget::updateFirmware()
 * @author yuluo
 * @details 升级固件
 */
void Widget::updateFirmware()
{
    switch (eUpateSte) {
        /* 空闲什么都不做 */
    case UPDATE_IDEL:
        break;

        /* 发送固件信息 */
    case UPDATE_SEND_FILEINFO:
        sendFirmwareInfo();
        /* 回到空闲等待状态 */
        eUpateSte = UPDATE_IDEL;
        break;

        /* 发送擦除指令 */
    case UPDATE_SEND_ERASE:
        /* 发送擦除的指令 */
        sendEraseCmd();
        ui->bar_progress->setValue(0);
        /* 未实现 暂时返回空闲 */
        eUpateSte = UPDATE_IDEL;
        break;

        /* 发送固件数据 */
    case UPDATE_SEND_FIRMWARE:
        sendFirmwareData();
        break;

        /* 更新完成 */
    case UPDATE_DONE:
        /* 需要在此处设置一些状态 */
        ui->btn_startupdate->setText("开始升级固件");
        /* 回到空闲等待状态 */
        eUpateSte = UPDATE_IDEL;
        break;

        /* 更新错误 */
    case UPDATE_ERROR:
        /* 显示对应错误的提示后返回 */
        switch (eErr) {
        case ERROR_NO:
            break;

        case ERROR_ACK:
            /* 提示 */
            QMessageBox::warning(
                                this,                 /*父窗口*/
                                "警告",                /*窗口标题*/
                                "设备未应答"     /*提示文本*/
                                );
            ui->btn_startupdate->setText("开始升级固件");
            /* 重置进度条 */
            ui->bar_progress->setValue(0);
            /* 重置发送ack */
            sendFirmwareDataAck = true;
            /* 初始化已发送的包数 */
            sentPackets = 0;
            break;

        case ERROR_CRC:
            /* 提示 */
            QMessageBox::warning(
                                this,                 /*父窗口*/
                                "警告",                /*窗口标题*/
                                "CRC校验错误"     /*提示文本*/
                                );
            ui->btn_startupdate->setText("开始升级固件");
            /* 重置进度条 */
            ui->bar_progress->setValue(0);
            /* 重置发送ack */
            sendFirmwareDataAck = true;
            /* 初始化已发送的包数 */
            sentPackets = 0;
        break;

        case ERROR_TIMEOUT:
            /* 提示 */
            QMessageBox::warning(
                                this,                 /*父窗口*/
                                "警告",                /*窗口标题*/
                                "应答超时"     /*提示文本*/
                                );
            ui->btn_startupdate->setText("开始升级固件");
            /* 重置进度条 */
            ui->bar_progress->setValue(0);
            /* 重置发送ack */
            sendFirmwareDataAck = true;
            /* 初始化已发送的包数 */
            sentPackets = 0;
            break;

        case ERROR_UPDATE_FAILED:
            /* 提示 */
            QMessageBox::warning(
                                this,                 /*父窗口*/
                                "警告",                /*窗口标题*/
                                "设备未应答 升级失败"     /*提示文本*/
                                );
            ui->btn_startupdate->setText("开始升级固件");
            /* 重置进度条 */
            ui->bar_progress->setValue(0);
            /* 重置发送ack */
            sendFirmwareDataAck = true;
            /* 初始化已发送的包数 */
            sentPackets = 0;
            break;

        default:break;

        }
        /* 回到空闲等待状态 */
        eUpateSte = UPDATE_DONE;
        break;

    default:break;

    }
}

/**
 * @brief Widget::onBtnClickedSlots()
 * @author yuluo
 * @details 按钮信号槽
 */
void Widget::onBtnClickedSlots()
{
    /* 获取信号的发送者 */
    QPushButton *btn = qobject_cast<QPushButton *>(sender());

    /* 帮助按钮 打开帮助文件 */
    if(btn == ui->btn_help)
    {
#if DEBUG_ENABLE
        qDebug("btn help is clicked");
#endif
        helpInfo *helpInformation = new helpInfo;
        helpInformation->setAttribute(Qt::WA_DeleteOnClose);
        helpInformation->show();
        // 调用它的函数来加载图片
        helpInformation->showImage(":/icon/BootLoader Deal V1.0.png");
    }
    /* 固件选择按钮 选择需要下载的固件 */
    else if(btn == ui->btn_selectdfu)
    {
#if DEBUG_ENABLE
        qDebug("btn selectdfu is clicked");
#endif
        /* 选则固件 */
        selectFirmware();
    }
    /* 连接设备 */
    else if(btn == ui->btn_connectdevice)
    {
#if DEBUG_ENABLE
        qDebug("btn connectdevice is clicked");
#endif
        /* 按钮没有连接时才进行连接 */
        if(ui->btn_connectdevice->text() == "连接设备")
        {
            connectSerial();
        }
        /* 设备断开 */
        else if(ui->btn_connectdevice->text() != "连接设备")
        {
            if(eUpateSte != UPDATE_IDEL)
            {
                /* 隐藏屏蔽鼠标点击事件的遮罩 */
                mask->hide();
                /* 恢复鼠标的状态 */
                QApplication::restoreOverrideCursor();
                /* 强制刷新鼠标 */
                QApplication::processEvents();
                /* 重置发送固件的应答 */
                sendFirmwareDataAck = true;
                /* 重置已发送的包数 */
                sentPackets = 0;
                /* 发送完成了 */
                eUpateSte = UPDATE_DONE;
                /* 提示 */
                QMessageBox::warning(
                                    this,                 /*父窗口*/
                                    "警告",                /*窗口标题*/
                                    "串口已关闭"              /*提示文本*/
                                    );
            }
            serial->closeSerialPort();
            ui->btn_startupdate->setText("开始升级固件");
            ui->btn_connectdevice->setText("连接设备");
            ui->btn_connectdevice->setIcon(QIcon(":/icon/disconnect.png"));
            ui->lab_deviceinfo->setText("--设备信息--");
            ui->lab_deviceinfo->setStyleSheet("color: gray");
            /* 关闭串口后断开串口相关的下拉框增加索引改变的槽函数 防止没有打开串口而修改参数导致的异常 */
            disconnect(ui->cbx_selectcom, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Widget::on_CbxCurrentIndexChangedSlots);
            /* 参数变回可选 */
            ui->cbx_bond->setEnabled(true);
            ui->cbx_databit->setEnabled(true);
            ui->cbx_stopbit->setEnabled(true);
            ui->cbx_paritybit->setEnabled(true);
        }
    }
    /* 开始升级 */
    else if(btn== ui->btn_startupdate && ui->btn_startupdate->text() == "开始升级固件")
    {
#if DEBUG_ENABLE
        qDebug("btn startupdate is clicked");
#endif
        /* 固件数据不为空才开始升级 */
        if(!firmwareData.isEmpty())
        {
#if DEBUG_ENABLE
        qDebug("UPDATE_SEND_FILEINFO");
#endif
            if(ui->btn_connectdevice->text() == "断开连接")
            {
                mask->show();
                /* 设置鼠标等待状态 */
                QApplication::setOverrideCursor(Qt::WaitCursor);
                ui->btn_startupdate->setText("正在升级 请勿断开连接");
                eUpateSte = UPDATE_SEND_FILEINFO;
            }
            else
            {
                /* 提示 */
                QMessageBox::warning(
                                    this,                 /*父窗口*/
                                    "警告",                /*窗口标题*/
                                    "设备连接错误"        /*提示文本*/
                                    );
            }
        }
        else
        {
            /* 提示 */
            QMessageBox::warning(
                                this,                 /*父窗口*/
                                "警告",                /*窗口标题*/
                                "请先选择固件"        /*提示文本*/
                                );
        }
    }
}


/**
 * @brief Widget::on_CbxCurrentIndexChangedSlots(int index)
 * @author yuluo
 * @details 下拉框发生变化时触发此槽函数
 */
void Widget::on_CbxCurrentIndexChangedSlots(int index)
{
    /* 获取发送信号的下拉框 */
    QComboBox *senderCbx = qobject_cast<QComboBox*>(sender());
    if (!senderCbx) return;

#if DEBUG_ENABLE
        qDebug("cbx Current Index is Changed");
#endif

    /* CRC下拉框发生变化更新校验和 */
    if(senderCbx == ui->cbx_crc)
    {
        /* 更新校验和 */
        updateCrcResult();
    }
    else if(senderCbx == ui->cbx_selectcom)
    {
        /* 选择新的串口后 重新连接串口 */
        connectSerial();
    }
}

/**
 * @brief Widget::on_SerialSlost(const QString &event, const QString &portName = QString());
 * @author yuluo
 * @details 串口相关的信号槽
 */
void Widget::on_SerialSlost(const QString &event, const QString &SerialPortName)
{
    if(event == QLatin1String("removed"))
    {
#if DEBUG_ENABLE
        qDebug("current serial port has removed");
#endif
//        mask->hide();
//        QApplication::restoreOverrideCursor();
//        QApplication::processEvents(); // 强制刷新事件循环
        disconnect(ui->cbx_selectcom, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Widget::on_CbxCurrentIndexChangedSlots);
    }

    if(event == QLatin1String("opened"))
    {
#if DEBUG_ENABLE
        qDebug("serial port open");
#endif
        /* 串口打开后重新设置连接按钮 */
        ui->btn_connectdevice->setText("正在连接设备");
        ui->btn_connectdevice->setIcon(QIcon(":/icon/connectfail.png"));
        /* 打开串口后给串口相关的下拉框增加索引改变的槽函数 防止在连接时更改参数 但是注意更改参数会先断开串口的连接 */
        connect(ui->cbx_selectcom, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Widget::on_CbxCurrentIndexChangedSlots);

        /* 串口一连上就应该获取开发板信息 */
        serial->sendDataPacketRetry(static_cast<quint8>(FUNC_VERSION_CODE),static_cast<quint8>(ACK_FUNC_VERSION_CODE),  QByteArray(), ui->cbx_crc->currentText());
    }
}

/**
 * @brief Widget::on_SerialConnectFaliSlost()
 * @author yuluo
 * @details 串口连接失败 实际是正在使用的串口被移除时触发
 */
void Widget::on_SerialConnectFaliSlost()
{
#if DEBUG_ENABLE
        qDebug("current serial port disconnected");
#endif
        /* 隐藏屏蔽鼠标点击事件的遮罩 */
        mask->hide();
        /* 恢复鼠标的状态 */
        QApplication::restoreOverrideCursor();
        /* 强制刷新鼠标 */
        QApplication::processEvents();
        /* 重置发送应答 */
        sendFirmwareDataAck = true;
        /* 重置发送的包数 */
        sentPackets = 0;
        /* 发送完成了 */
        eUpateSte = UPDATE_DONE;
        /* 提示 */
        QMessageBox::warning(
                            this,                 /*父窗口*/
                            "警告",                /*窗口标题*/
                            "串口已断开"              /*提示文本*/
                            );
        ui->btn_startupdate->setText("开始升级固件");
        serial->closeSerialPort();
        /* 关闭串口后断开串口相关的下拉框增加索引改变的槽函数 防止没有打开串口而修改参数导致的异常 */
        disconnect(ui->cbx_selectcom, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Widget::on_CbxCurrentIndexChangedSlots);
        /* 参数变回可选 */
        ui->cbx_bond->setEnabled(true);
        ui->cbx_databit->setEnabled(true);
        ui->cbx_stopbit->setEnabled(true);
        ui->cbx_paritybit->setEnabled(true);
        /* 串口被移除后恢复初始显示 */
        ui->btn_connectdevice->setText("连接设备");
        ui->btn_connectdevice->setIcon(QIcon(":/icon/disconnect.png"));
        ui->lab_deviceinfo->setText("--设备信息--");
        ui->lab_deviceinfo->setStyleSheet("color: gray");
}

/**
 * @brief Widget::on_SerialNoAckSlost(quint8 funcCode)
 * @param 发送出去的功能码
 * @author yuluo
 * @details 串口无应答信号
 */
void Widget::on_SerialNoAckSlost(quint8 funcCode)
{
    mask->hide();
    QApplication::restoreOverrideCursor();
    QApplication::processEvents(); // 强制刷新事件循环
    if(funcCode == FUNC_VERSION_CODE)
    {
        /* 提示 */
        QMessageBox::information(
                            this,                 /*父窗口*/
                            "注意",                /*窗口标题*/
                            "通信协议错误"              /*提示文本*/
                            );
        /* 串口打开后重新设置连接按钮 */
        ui->btn_connectdevice->setText("设备连接错误");
        ui->btn_connectdevice->setIcon(QIcon(":/icon/connectfail.png"));
        ui->lab_deviceinfo->setText("获取芯片信息失败 请检查通信协议\n点击右上角图片按钮获取帮助信息");
        ui->lab_deviceinfo->setStyleSheet("color: red");
    }
    else if(funcCode == FUNC_FIRMWAREINFO_CODE)
    {
        /* 未接收到固件信息的应答 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记NO ACK错误 */
        eErr = ERROR_ACK;
    }
    else if(funcCode == FUNC_ERASE_CODE)
    {
        /* 未接收到固件信息的应答 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记NO ACK错误 */
        eErr = ERROR_ACK;
    }
    else if(funcCode == FUNC_FIRMWAREDATA_CODE)
    {
        /* 未接收到应答 升级失败 */
        sendFirmwareDataAck = false;
        /* 失败了也要重置已发送的包数 */
        sentPackets = 0;
        /* 未接收到固件信息的应答 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记NO ACK错误 */
        eErr = ERROR_UPDATE_FAILED;
    }
}

/**
 * @brief Widget::on_SerialAckSlost(quint8 funcCode, const QByteArray &data)
 * @param 等待应答的功能码
 * @param 接收到的数据包
 * @author yuluo
 * @details 串口应答信号
 */
void Widget::on_SerialAckSlost(quint8 funcCode, const QByteArray &data)
{
    /* 拿到芯片信息了 */
    if(funcCode == ACK_FUNC_VERSION_CODE)
    {
        /* 串口打开后重新设置连接按钮 */
        ui->btn_connectdevice->setText("断开连接");
        ui->btn_connectdevice->setIcon(QIcon(":/icon/connect.png"));

#if DEBUG_ENABLE
        qDebug() << "收到数据包:" << data.toHex(' ');
#endif
        QString version = QString("BootLoader Version V%1.%2.%3")
                                .arg((uint8_t)data[0])
                                .arg((uint8_t)data[1])
                                .arg((uint8_t)data[2]);
        ui->lab_deviceinfo->setText(version);
        ui->lab_deviceinfo->setStyleSheet("color: rgb(0, 0, 0)");
    }
    else if(funcCode == ACK_FUNC_FIRMWAREINFO_CODE)
    {
        /* 接收到固件信息的应答 进入擦除指令 */
        eUpateSte = UPDATE_SEND_ERASE;
    }
    else if(funcCode == ACK_FUNC_ERASE_CODE)
    {
        whichTimeStrike = 0;
        /* 设置需要等待的应答 */
        serial->setWaitFuncCode(ACK_FUNC_ERASEDONE_CODE);
        /* 开启定时器 等待擦除完成的指令 超时时间10s */
        timeOutTimer->start(10000);
#if DEBUG_ENABLE
        qDebug() << "开启定时器等待擦除完成";
#endif
    }
    else if(funcCode == ACK_FUNC_ERASEDONE_CODE)
    {
#if DEBUG_ENABLE
        qDebug() << "擦除完成";
#endif
        /* 断开槽函数 */
//        disconnect(timeOutTimer, &QTimer::timeout, this, &Widget::on_TimeOutFunction);
        /* 未超时 但是擦除完成了 也要关闭定时器 */
        timeOutTimer->stop();
        /* 接收到擦除完成的信号应答 进入数据发送 */
        eUpateSte = UPDATE_SEND_FIRMWARE;
    }
    else if(funcCode == ACK_FUNC_FIRMWAREDATA_CODE)
    {
        /* 已经收到应答了 可用发送下一帧了 */
        sendFirmwareDataAck = true;
    }
    else if(funcCode == ACK_FUNC_FIRMWARECRCOK_CODE)
    {
        /* 等到了ACK */
        timeOutTimer->stop();
        /* 校验成功 */
        /* 隐藏屏蔽鼠标点击事件的遮罩 */
        mask->hide();
        /* 恢复鼠标的状态 */
        QApplication::restoreOverrideCursor();
        /* 强制刷新鼠标 */
        QApplication::processEvents();
        /* 超时后重置固件发送的ack状态 防止下次点击升级时失败 */
        sendFirmwareDataAck = true;
        /* 重置已发送的包数 */
        sentPackets = 0;
        /* 发送完成了 */
        eUpateSte = UPDATE_DONE;
        /* 提示 */
        QMessageBox::information(
                            this,                 /*父窗口*/
                            "提示",                /*窗口标题*/
                            "升级完成"              /*提示文本*/
                            );
    }
}

/**
 * @brief Widget::on_ChechsumError(quint8 funcCode)
 * @param 等待应答的功能码
 * @author yuluo
 * @details 串口应答错误信号
 */
void Widget::on_ChechsumError(quint8 funcCode)
{
    mask->hide();
    QApplication::restoreOverrideCursor();
    QApplication::processEvents(); // 强制刷新事件循环
    if(funcCode == ACK_FUNC_VERSION_CODE)
    {
        ui->btn_connectdevice->setText("设备连接错误");
        ui->btn_connectdevice->setIcon(QIcon(":/icon/connectfail.png"));
        ui->lab_deviceinfo->setText("校验码错误！选择正确的CRC校验算法");
        ui->lab_deviceinfo->setStyleSheet("color: red");
    }
    else if(funcCode == ACK_FUNC_FIRMWAREINFO_CODE)
    {
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记CRC错误 */
        eErr = ERROR_CRC;
    }
    else if(funcCode == ACK_FUNC_ERASEDONE_CODE)
    {
        /* 擦除完成的应答信号 但是校验和错误 */
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记CRC错误 */
        eErr = ERROR_CRC;
    }
    else if(funcCode == ACK_FUNC_FIRMWAREDATA_CODE)
    {
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 升级失败 */
        eErr = ERROR_UPDATE_FAILED;
        /* 超时后重置固件发送的ack状态 防止下次点击升级时失败 */
        sendFirmwareDataAck = true;
        /* 重置已发送的包数 */
        sentPackets = 0;
    }
    else if(funcCode == ACK_FUNC_FIRMWARECRCOK_CODE)
    {
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_IDEL;
        /* 超时后重置固件发送的ack状态 防止下次点击升级时失败 */
        sendFirmwareDataAck = true;
        /* 重置已发送的包数 */
        sentPackets = 0;
        /* 提示 */
        QMessageBox::warning(
                            this,                 /*父窗口*/
                            "警告",                /*窗口标题*/
                            "校验失败"              /*提示文本*/
                            );
    }
}

/**
 * @brief Widget::on_ThreadFunction()
 * @author yuluo
 * @details 定时器信号槽 模拟线程用的
 */
void Widget::on_ThreadFunction()
{
    /* 升级固件 */
    updateFirmware();
}

/**
 * @brief Widget::on_TimeOutFunction()
 * @author yuluo
 * @details 等待指令超时
 */
void Widget::on_TimeOutFunction()
{
    if(whichTimeStrike == 0)
    {
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记CRC错误 */
        eErr = ERROR_TIMEOUT;
    }
    else if(whichTimeStrike == 1)
    {
        /* 超时后重置固件发送的ack状态 防止下次点击升级时失败 */
        sendFirmwareDataAck = true;
        /* 重置已发送的包数 */
        sentPackets = 0;
        /* 应答校验错误 进入错误 */
        eUpateSte = UPDATE_ERROR;
        /* 标记CRC错误 */
        eErr = ERROR_TIMEOUT;
    }
    /* 隐藏屏蔽鼠标点击事件的遮罩 */
    mask->hide();
    /* 恢复鼠标的状态 */
    QApplication::restoreOverrideCursor();
    /* 强制刷新鼠标 */
    QApplication::processEvents();
    /* 停止定时器 */
    timeOutTimer->stop();
}
